Skip to content

fix(backend): unbreak main — ws-b lifecycle tests rotted when the wall clock passed their pinned NOW + TTL#10169

Open
arhxam wants to merge 1 commit into
BasedHardware:mainfrom
arhxam:fix/ws-b-lifecycle-stale-now-anchor
Open

fix(backend): unbreak main — ws-b lifecycle tests rotted when the wall clock passed their pinned NOW + TTL#10169
arhxam wants to merge 1 commit into
BasedHardware:mainfrom
arhxam:fix/ws-b-lifecycle-stale-now-anchor

Conversation

@arhxam

@arhxam arhxam commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

main is currently red, and this is why

Every recent Backend Unit Tests run on main fails with the same 7 failures in tests/unit/test_ws_b_short_term_lifecycle.py. No code change caused it — the suite rotted on a date.

The file pins:

NOW = datetime(2026, 6, 20, 12, 0, tzinfo=timezone.utc)

Seven cases persist an item with write_canonical_extraction_memory, which takes no clock parameter and stamps captured_at from the real clock, then drive processing with now=NOW. The processed patch sets:

expires_at = default_short_term_expiry(now)   # now + DEFAULT_SHORT_TERM_TTL_DAYS (30)

So expires_at was frozen at 2026-06-20 + 30d = 2026-07-20, while captured_at tracked real time. MemoryItem.validate_tier_invariants requires:

if self.expires_at <= self.captured_at:
    raise ValueError("short_term expires_at must be after captured_at")

That held until the wall clock passed 2026-07-20 — and then stopped.

Observed directly:

DEBUG id=manual-required-processed tier=short_term
      captured_at=2026-07-21 00:48:56+00:00     <- real clock
      expires_at=2026-07-20 12:00:00+00:00      <- NOW + 30d
real now      : 2026-07-21
hardcoded NOW : 2026-06-20
NOW + 30d TTL : 2026-07-20   <- bomb armed once real now passed this

All seven failures surface as error_code='ValidationError':

FAILED test_required_processing_receipt_unlocks_durable_promotion
FAILED test_required_processing_receipt_is_bound_to_current_content_and_revision
FAILED test_required_processor_retry_rebases_operation_after_unrelated_head_advance
FAILED test_inflight_promotion_cannot_overwrite_negative_user_review
FAILED test_required_promotion_merges_exact_existing_long_term
FAILED test_required_promotion_merges_multiple_sources_in_same_run
FAILED test_required_promotion_retry_after_supersede_failure_is_idempotent_across_run_ids

Fix

Anchor NOW to the real clock (today at 12:00 UTC) so the injected clock and the real captured_at can never drift apart by more than the TTL:

NOW = datetime.now(timezone.utc).replace(hour=12, minute=0, second=0, microsecond=0)

NOW remains a single fixed value for the whole run, so the tests stay deterministic — it simply no longer rots.

Test-only change. The production behaviour isn't at fault: the mismatch is between the test's injected clock and the un-injectable one inside write_canonical_extraction_memory. Giving that function a now parameter would be the deeper fix, but it changes a production signature to serve a test, so I've left it — happy to do it that way if you'd prefer.

Verification

$ pytest tests/unit/test_ws_b_short_term_lifecycle.py
25 passed          # restoring the fixed date reproduces exactly the 7 CI failures

module-scope sys.modules gate : 756 files, 0 violations
line-count ratchet            : no increase
black --line-length 120       : clean
fast-unit CPU                 : 0.02s max (limit 1.00s)

Heads-up on the same shape elsewhere

Five other unit files pin the same 2026-06-20 date — test_canonical_consolidation, test_canonical_maintenance_ordering, test_canonical_consolidation_apply, test_canonical_kg_promotion, test_v3_canary_approval_artifact. They pass today because none mixes a real-clock write with a TTL computed off NOW, but they carry the same latent shape. I've left them alone rather than widen this PR.

Failure class

Failure-Class: none

A test clock that goes stale against a real-clock write; no registered class covers it.

Review in cubic

tests/unit/test_ws_b_short_term_lifecycle.py pinned

    NOW = datetime(2026, 6, 20, 12, 0, tzinfo=timezone.utc)

Seven of its cases persist an item with write_canonical_extraction_memory,
which takes no clock parameter and stamps captured_at from the real clock, and
then drive processing with now=NOW. The processed patch sets

    expires_at = default_short_term_expiry(now)   # now + DEFAULT_SHORT_TERM_TTL_DAYS (30)

so expires_at was fixed at 2026-06-20 + 30d = 2026-07-20 while captured_at
tracked real time. MemoryItem.validate_tier_invariants requires

    short_term expires_at must be after captured_at

which held until the wall clock passed 2026-07-20 and then stopped holding. The
suite began failing on its own, with no code change, and every Backend Unit
Tests run on main has been red since:

    FAILED test_required_processing_receipt_unlocks_durable_promotion
    FAILED test_required_processing_receipt_is_bound_to_current_content_and_revision
    FAILED test_required_processor_retry_rebases_operation_after_unrelated_head_advance
    FAILED test_inflight_promotion_cannot_overwrite_negative_user_review
    FAILED test_required_promotion_merges_exact_existing_long_term
    FAILED test_required_promotion_merges_multiple_sources_in_same_run
    FAILED test_required_promotion_retry_after_supersede_failure_is_idempotent_across_run_ids

all with error_code='ValidationError' from that invariant.

Fix: anchor NOW to the real clock (today at 12:00 UTC) so the injected clock
and the real captured_at can never drift apart by more than the TTL. NOW stays
a single fixed value for the whole run, so the tests remain deterministic; it
simply no longer rots.

The production behaviour is not at fault here — the mismatch is between the
test's injected clock and the un-injectable one inside
write_canonical_extraction_memory, so this is a test-only change.

Note: five other unit files pin the same 2026-06-20 date
(test_canonical_consolidation, test_canonical_maintenance_ordering,
test_canonical_consolidation_apply, test_canonical_kg_promotion,
test_v3_canary_approval_artifact). They pass today because none of them mixes a
real-clock write with a TTL computed off NOW, but they carry the same latent
shape and are worth the same treatment if they ever start failing.

Verification: 25 pass on this file; restoring the fixed date reproduces exactly
the 7 failures CI reports on main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks @arhxam — clean, well-documented fix for a genuine time-bomb in the suite.

Your diagnosis is correct: the tests pinned NOW to a fixed calendar date (2026-06-20) while write_canonical_extraction_memory stamps captured_at from the real clock, so once the wall clock passed NOW + 30-day TTL (2026-07-20), the expires_at > captured_at invariant broke on its own — with no code change. Anchoring NOW to datetime.now(timezone.utc) with normalized h/m/s is the right pragmatic fix to unbreak main; the 30-day window comfortably keeps expires_at ahead of real captured_at.

The directly relevant Backend unit suite check passes on this head. (Hygiene/Formatting show as cancelled, which looks like the expected path-filter behavior for a backend-only test change — worth a maintainer confirming the branch-protection view is green before merge.)

Optional follow-up (not blocking): the deeper root cause is that write_canonical_extraction_memory has no injectable clock, which forces tests to mix real-clock captured_at with a synthetic now. Threading a clock parameter through that function would let these tests be fully deterministic again and free NOW from datetime.now(). Worth a separate issue if you want to track it.

Leaving a positive signal rather than a formal approval. Appreciate the quick unblock of main.


by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.

@kodjima33 kodjima33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backend fix that UN-BREAKS main: ws_b lifecycle tests rotted when wall clock passed pinned NOW+TTL — approve only; DIRTY, needs rebase by author to land and green main

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-maintainer-review Needs a human maintainer to sign off before merge positive-signal Good PR — positive signal, not a formal approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants